package com.director.spring;

import com.director.core.DirectAction;
import com.director.core.DirectConfiguration;
import com.director.core.DirectException;
import com.director.core.DirectMethod;
import com.director.core.DirectTransactionData;
import com.director.core.DirectTransactionResult;
import com.director.core.ExecutorAdapter;
import org.apache.commons.lang.StringUtils;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Simone Ricciardi
 * @version 1.0, 11/07/2010
 */
public class SpringDirectExecutorAdapter implements ApplicationContextAware, BeanFactoryPostProcessor, ExecutorAdapter {

   private DirectConfiguration configuration;
   private Map<String, Map<String, String>> actionMapping = new HashMap<String, Map<String, String>>();
   private ApplicationContext applicationContext;

   @Required
   public void setConfiguration(DirectConfiguration configuration) {
      this.configuration = configuration;
      this.configuration.registerAdapter(this);
   }

   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      String beanNames[] = beanFactory.getBeanDefinitionNames();
      for(String beanName : beanNames) {
         BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

         Class<? extends Object> beanClass;
         try {
            beanClass = Class.forName(beanDefinition.getBeanClassName());
         } catch(ClassNotFoundException e) {
            throw new FatalBeanException("Class not found for bean: " + beanName);
         }

         if(beanClass.isAnnotationPresent(com.director.annotation.DirectAction.class)) {
            com.director.annotation.DirectAction directAction = beanClass.getAnnotation(com.director.annotation.DirectAction.class);
            String nameSpace = directAction.namespace();
            String actionName = StringUtils.isNotBlank(directAction.name()) ? directAction.name() : beanClass.getSimpleName();
            String className = beanClass.getName();

            this.configuration.registerClass(className, nameSpace, actionName);
            Map<String, String> nsMapping = this.actionMapping.get(nameSpace);
            if(nsMapping == null) {
               nsMapping = new HashMap<String, String>();
               this.actionMapping.put(nameSpace, nsMapping);
            }
            nsMapping.put(actionName, beanName);
         }
      }
   }

   public DirectTransactionResult execute(DirectAction directAction, DirectMethod directMethod, DirectTransactionData data)
         throws DirectException {
      Map<String, String> nsMapping = this.actionMapping.get(directAction.getNamespace());
      String beanName = nsMapping.get(directAction.getName());
      Object bean = this.applicationContext.getBean(beanName);
      ExecutorAdapter beanExecutorAdapter = this.buildBeanExecutorAdapter(bean);
      return beanExecutorAdapter.execute(directAction, directMethod, data);
   }

   private ExecutorAdapter buildBeanExecutorAdapter(Object action) {
      return (action instanceof Advised) ?
            new AdvisedActionExecutorAdapter((Advised) action) : new DefaultActionExecutorAdapter(action);
   }
}
